-- Copyright (c) 2025 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local log = require 'mc.logging'
local context = require 'mc.context'

local MAC_ADDRESS_OFFSET <const> = 9
local PORT_INFO_STATUS_SPEED_OFFSET <const> = 64
local FIRMWARE_VERSION_OFFSET <const> = 38
local PORT_TEMP_OFFSET <const> = 51
local PORT_LLDP_OFFSET <const> = 80
local PORT_NEGOTIATION_OFFSET <const> = 72
local CARD_LLDP_OFFSET <const> = 88

local JM800_chip = {}

JM800_chip.__index = JM800_chip

function JM800_chip.new(chip)
    return setmetatable({ chip = chip}, JM800_chip)
end

local function string_to_number(str)
    local numbers = {}
    local byte
    if not str then
        return numbers
    end

    for i = 1, #str do
        byte = string.byte(str, i)
        numbers[#numbers + 1] = tonumber(string.format("%d", byte))
    end

    return numbers
end

local function chip_read(chip, read_offset, read_len)
    local retry_count = 3
    for _ = 1, retry_count do
        local ok, recv_data = pcall(function ()
            return chip:Read(context.get_context_or_default(), read_offset, read_len)
        end)
        if ok then
            return string_to_number(recv_data)
        end
    end
    return false
end

function JM800_chip:GetNicCardInfo(offset, cb)
    local chip_data = chip_read(self.chip, offset, 1)
    if not chip_data or #chip_data ~= 1 then
        return false
    end
    return cb(chip_data)
end

local function chip_write(chip, write_offset, data)
    local retry_count = 3
    local ok, err
    for _ = 1, retry_count do
        ok, err = pcall(function ()
            return chip:Write(context.get_context_or_default(), write_offset, data)
        end)
        if ok then
            return true
        else
            log:error('chip write error: %s', err)
        end
    end
    return false
end

function JM800_chip:SetPortLLDPEnable(port, value)
    local data = 0x0
    local write_data = {}
    local temp

    if port.PortID < 0 or port.PortID > 7 then
        return false
    end

    local chip_data = chip_read(self.chip, PORT_LLDP_OFFSET, 8)
    if not chip_data then
        return false
    end

    for i = 1, #chip_data do
        local byte = chip_data[i]
        local lldp_status = 0xff
        if byte ~= 0xff then
            lldp_status = (byte >> 3) & 0x03 -- bit4~3
        end
        if lldp_status == 1 then
            data = data | (1 << (i - 1))
        else
            temp = 1 << (i - 1)
            data = data & (~temp)
        end
    end

    if value then
        data = data | (1 << port.PortID)
    else
        temp = 1 << port.PortID
        data = data & (~temp)
    end
    write_data[#write_data + 1] = data
    
    local ret = chip_write(self.chip, CARD_LLDP_OFFSET, write_data)
    if not ret then
        log:notice("SetPortLLDPEnable PortID= %s data = %s, ret = %s", port.PortID, data, ret)
    end
    return ret
end

function JM800_chip:GetMacAddress()
    local mac_info = chip_read(self.chip, MAC_ADDRESS_OFFSET, 6)
    if not mac_info then
        return nil
    end

    return mac_info
end

function JM800_chip:GetLinkStatus()
    local port_infos = chip_read(self.chip, PORT_INFO_STATUS_SPEED_OFFSET, 8)
    if not port_infos then
        return nil
    end

    local port_map = {}
    for idx, port_info in ipairs(port_infos) do
        -- 非无效值，对应port 赋值为连接和断开，无效值场景赋值为N/A
        if port_info ~= 0xff then
            if port_info & 0x10 == 0 then
                port_map[idx] = 1
            else
                port_map[idx] = 0
            end
        else
            port_map[idx] = 255
        end
    end
    return port_map
end

function JM800_chip:GetPortSpeed()
    local speed_map = {10000, 25000, 40000, 50000, 100000, 200000, 400000}

    local port_map = {}
    local speed_index

    local port_speeds = chip_read(self.chip, PORT_INFO_STATUS_SPEED_OFFSET, 8)
    if not port_speeds then
        return nil
    end

    for idx, port_info in ipairs(port_speeds) do
        -- 非无效值，对应port赋值为真实速率，无效值场景赋值为0
        if port_info ~= 0xff then
            speed_index = port_info & 0xf
            if speed_index > 0 and speed_index <= #speed_map then
                port_map[idx] = speed_map[speed_index]
            end
        else
            port_map[idx] = 0
        end
    end
    return port_map
end

local function parse_LLDPinfo(lldp_info)
    local ports_map = {}
    for i = 1, #lldp_info do
        local byte = lldp_info[i]
        if byte ~= 0xff then
            local lldp_status = (byte >> 3) & 0x03 -- bit4~3
            local lldp_mode = byte & 0x07 -- bit2~0

            ports_map[i - 1] = {
                lldp_status = lldp_status,
                lldp_mode = lldp_mode
            }
        end
    end
    return ports_map
end

function JM800_chip:GetLLDPInfo()
    local lldp_info_origin = chip_read(self.chip, PORT_LLDP_OFFSET, 8)
    if not lldp_info_origin then
        return nil
    end
    local lldp_info = parse_LLDPinfo(lldp_info_origin)
    return lldp_info
end

local function parse_negotiation(negotiation_info)
    local ports_map = {}
    local port_number
    local negotiation_mode
    local negotiation_type
    local byte

    for i = 1, #negotiation_info do
        byte = negotiation_info[i]
        if byte ~= 0xff then
            port_number = (byte >> 5) & 0x07 -- bit7~5
            negotiation_mode = (byte >> 2) & 0x07 -- bit4~2
            negotiation_type = byte & 0x03 -- bit 1~0

            ports_map[port_number] = {
                full_duplex = (negotiation_type == 0),
                auto_negatiation = (negotiation_mode == 1)
            }
        end
    end
    return ports_map
end

function JM800_chip:GetNegotiationInfo()
    local nego_info = chip_read(self.chip, PORT_NEGOTIATION_OFFSET, 8)
    if not nego_info then
        return nil
    end
    local negotiation_info = parse_negotiation(nego_info)
    return negotiation_info
end

function JM800_chip:GetFirmwareVersion()
    local version = chip_read(self.chip, FIRMWARE_VERSION_OFFSET, 3)
    if not version then
        return nil
    end
    return version
end

local function to_sign_int8(num)
    local value = num & 0x7f
    if (num & 0x80) ~= 0 then
        value = -value
    end
    return value
end

function JM800_chip:GetPortTemp()
    local port_temps = chip_read(self.chip, PORT_TEMP_OFFSET, 6)
    if not port_temps then
        return nil
    end
    local port_map_temp = {}
    for index, port_temp in ipairs(port_temps) do
        if port_temp ~= 0xff then
            port_map_temp[index] = to_sign_int8(port_temp)
        else
            -- 对于不在位的光模块，返回10度，不影响最值计算
            port_map_temp[index] = 10
        end
    end

    return port_map_temp
end

local MCU = { oem_smbus = JM800_chip}

return MCU